package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    //根据查询条件进行数据查询
    public Map search(Map<String, String> searchmap) {

        Map<String,Object> map=new HashMap<>();
        //构建查询
        if (searchmap!=null){
            //开启查询
            //2构建查询条件封装对象(这里面封装查询条件)
            NativeSearchQueryBuilder nativeSearchQueryBuilder=new NativeSearchQueryBuilder();
            //4为什么要构建boolquery查询，因为下面要多条件拼接
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //按照关键字查询
            if (StringUtils.isNotEmpty(searchmap.get("keywords"))){
                                                    //matchquery相当于模糊查询，下面的termquery相当于精确查询
                QueryBuilder queryBuilder=QueryBuilders.matchQuery("name",searchmap.get("keywords")).operator(Operator.AND);
                boolQuery.must(queryBuilder);
            }

            //按照品牌进行过滤查询
            if (StringUtils.isNotEmpty(searchmap.get("brand"))){
                //termQuery完全字段查询(相当于精确查询)，第一个参数:要按照索引库中哪一个域来进行查询
                boolQuery.filter(QueryBuilders.termQuery("brandName",searchmap.get("brand")));
            }

            //按照规格进行过滤查询
            for (String key : searchmap.keySet()) {//得到所有的key,判断哪个key中包含需要的东西
                if (key.startsWith("spec_")){//可以看到postman相对应域里面的数据是"spec": "{'尺寸': '金色', '版本': '55英寸'}",我发送的请求格式是localhost:9009/search?keywords=电视&spec_尺寸=金色
                    String value = searchmap.get(key).replace("%2B", "+");//因为网络传输的原因要转换,这里拿到上面的金色
                    //spec_手机制式,这里面我要截取手机制式和其他两部分拼接,形成我要在索引库查询的域名,
                    boolQuery.filter(QueryBuilders.termQuery(("specMap."+key.substring(5)+".keyword"),value));
                }
            }

            //3
            nativeSearchQueryBuilder.withQuery(boolQuery);

            //按照品牌进行分组(聚合)查询,因为查手机就是手机的品牌，查电视就是电视的品牌，这是动态的,所以这里进行分组查询
            String skuBrand="skuBrand";
            //addAggregation代表要添加聚合,term表示数据分组查询后的列名叫什么,field表示按照哪个域来进行分组
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));//这里传到下面，然后最后封装返回结果

            //按照规格聚合(分组)查询,规格信息应该像品牌一样随着搜索条件的改变而改变
            String skuSpec="skuSpec";
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));

            /*第一个参数:条件构建对象
            * 第二个参数:查询操作实体类
            * 第三各参数:查询结果操作对象
            * */
            //1

            AggregatedPage<SkuInfo> resultInfos = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class, new SearchResultMapper() {
                @Override
                public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                    List<T> skuInfoList=new ArrayList<>();
                    //查询结果操作(要做的就是将查询结果装换为skuinfo对象进行保存)
                    SearchHits hits = searchResponse.getHits();
                    if (hits!=null){
                        for (SearchHit hit : hits) {
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
                            skuInfoList.add((T) skuInfo);
                        }
                    }
                    /*SearchHit[] searchHits = hits.getHits();
                    if (searchHits!=null){
                        for (SearchHit hit : searchHits) {
                            SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
                            skuInfoList.add(skuInfo);
                        }
                    }*/
                    return new AggregatedPageImpl<T>(skuInfoList,pageable,hits.getTotalHits(),searchResponse.getAggregations());
                }
            });

            //封装最终的返回结果,总记录数,总页数，数据集合
            map.put("total",resultInfos.getTotalElements());
            map.put("totalPages",resultInfos.getTotalPages());
            map.put("rows",resultInfos.getContent());

            //封装品牌的分组结果,这里不用for循环的方式了,用流运算的方式提高效率,效率会提高很多
            StringTerms brandTerms = (StringTerms) resultInfos.getAggregation(skuBrand);
            List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            map.put("brandList",brandList);/*测试结果,条件查询后多了一项数据   "brandList": ["华为","小米","Apple","OPPO","vivo","一加","三星","谷戈","魅族","诺基亚"],条件换成电视后,动态变成电视的品牌*/
            //封装规格的分组结果
            StringTerms stringTerms= (StringTerms) resultInfos.getAggregation(skuSpec);
                            //一样还是要以流运算的形式将里面的内容转换成集合
            List<String> specList = stringTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
            map.put("specList",specList);/*测试结果,又多了一项数据"specList": ["{'尺寸': '55寸'}","{'尺寸': '32寸'}","{'尺寸': '60寸', '版本': '标配'}","{'尺寸': '2寸'}","{'尺寸': '4寸'}","{'尺寸': '75寸', '版本': '标配'}","{'尺寸': '42寸'}","{}","{'尺寸': '60寸'}","{'尺寸': '55寸', '版本': '标配'}"
            postman测试条件localhost:9009/search?keywords=电视*/

            return map;
        }
        return null;
    }
}
